//#define __COMPILE_DEBUG__
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>

#include "list.h"
#include "zepto.h"

static char *delim = "()\"\' \t\r\n;";

#define CURRENT                                 \
  current

// This macro virtually extends stream beyond EOF
// by one space character. It is needed to let
// tokenizer to finish tokenizing of correctly
// formed S-expression
#define NEXT                                    \
  {                                             \
    current = _stream_getc(stream);             \
    if(current == EOF) {                        \
      if(has_eof) {                             \
        ret = ZEPTO_TOK_UNEXPECTED_EOF;         \
        goto out;                               \
      } else {                                  \
        current = ' ';                          \
        has_eof = true;                         \
      }                                         \
    }                                           \
  }           

#define NEWTOKEN                                    \
  {                                                 \
    token = malloc(sizeof(struct token));           \
    if(!token) {                                    \
      ret = ZEPTO_TOK_ENOMEM;                       \
      goto out;                                     \
    }                                               \
    DEBUG("%p", token);                             \
    list_add_tail(token, last);                     \
    DEBUG("%p", token);                             \
    last = (struct list_head *)token;               \
    produced++;                                     \
  }

#define ADDCHAR(c)                                \
  {                                               \
    char *tmp;                                    \
    if(string_buf_pos == string_buf_len) {        \
      string_buf_len *= 2;                        \
      tmp = realloc(string_buf, string_buf_len);  \
      if(!tmp) {                                  \
        ret = ZEPTO_TOK_ENOMEM;                   \
        goto out;                                 \
      }                                           \
      string_buf = tmp;                           \
    }                                             \
    string_buf[string_buf_pos++] = (c);           \
  }

#define LASTCHAR                                \
  (string_buf[string_buf_pos - 1])

#define PUTSTRING                                                       \
  {                                                                     \
    int string_len;                                                     \
    ADDCHAR('\0');                                                      \
    string_buf_pos = 0;                                                 \
    string_len = strlen(string_buf) + 1;                                \
    token->data.symbol = malloc(string_len);                            \
    if(!token->data.symbol) {                                           \
      ret = ZEPTO_TOK_ENOMEM;                                           \
      goto out;                                                         \
    }                                                                   \
    memcpy(token->data.symbol, string_buf, string_len);                 \
    DEBUG("PUTSTRING: %s", string_buf);                                 \
  }


#define PUTSYMBOL                                                       \
  {                                                                     \
    int string_len;                                                     \
    ADDCHAR('\0');                                                      \
    string_buf_pos = 0;                                                 \
    string_len = strlen(string_buf) + 1;                                \
    if(string_len == 2 && string_buf[0] == '.') {                       \
      token->type = TOK_DOT;                                            \
      DEBUG("DOT");                                                     \
    } else if (string_buf[0] == '#') {                                  \
      if(string_len == 3 && toupper(string_buf[1]) == 'T') {            \
        token->type = TOK_TRUE;                                         \
        DEBUG("TRUE");                                                  \
      } else if(string_len == 3 && toupper(string_buf[1]) == 'F') {     \
        token->type = TOK_FALSE;                                        \
        DEBUG("FALSE");                                                 \
      } else {                                                          \
        ret = ZEPTO_TOK_INVALID_SEQ;                                    \
        goto out;                                                       \
      }                                                                 \
    } else {                                                            \
      int i;                                                            \
      token->data.symbol = malloc(string_len);                          \
      if(!token->data.symbol) {                                         \
        ret = ZEPTO_TOK_ENOMEM;                                         \
        goto out;                                                       \
      }                                                                 \
      for(i = 0; i < string_len - 1; i++) {                             \
        string_buf[i] = toupper(string_buf[i]);                         \
      }                                                                 \
      memcpy(token->data.symbol, string_buf, string_len);               \
      DEBUG("PUTSYMBOL: %s", string_buf);                               \
    }                                                                   \
  }

#define CHECKNUMBER                                       \
  {                                                       \
    if((base == 10) && string_buf_pos == 1 &&             \
       LASTCHAR == '0') {                                 \
      base = 8;                                           \
    } else if((base == 8) && string_buf_pos == 2 &&       \
              toupper(LASTCHAR) == 'X') {                 \
      base = 16;                                          \
    } else if((base == 8 &&                               \
               (LASTCHAR > '8' || LASTCHAR < '0')) ||     \
              (base == 10 && !isdigit(LASTCHAR)) ||       \
              (base == 16 && !isxdigit(LASTCHAR))) {      \
      ret = ZEPTO_TOK_MALFORMED_NUM;                      \
      goto out;                                           \
    }                                                     \
  }

#define PUTNUMBER                                               \
  {                                                             \
    char *endptr;                                               \
    char *startptr = string_buf;                                \
    if(base == 8) {                                             \
      startptr++;                                               \
      if(string_buf_pos == 1) {                                 \
        ADDCHAR('0');                                           \
      }                                                         \
    } else if(base == 16) {                                     \
      startptr += 2;                                            \
      if(string_buf_pos < 3) {                                  \
        ret = ZEPTO_TOK_MALFORMED_NUM;                          \
        goto out;                                               \
      }                                                         \
    }                                                           \
    errno = 0;                                                  \
    ADDCHAR('Z');                                               \
    token->data.integer = strtoll(startptr, &endptr, base);     \
    if(errno || endptr != &string_buf[string_buf_pos - 1]) {    \
      ret = ZEPTO_TOK_MALFORMED_NUM;                            \
      goto out;                                                 \
    }                                                           \
    DEBUG("Int value: %ld", token->data.integer);               \
    string_buf_pos = 0;                                         \
    base = 10;                                                  \
  }                                                             \


// TODO: add (a.b) notation, 'notation
int _stream_tokenize(struct list_head *list, stream_t *stream) 
{
  state_t state = ST_SPACE; // state of the machine
  struct list_head *last;
  int ret = 0;
  struct token *token = NULL;
  char *string_buf;
  int string_buf_len = DEFAULT_STR_LEN;
  int string_buf_pos = 0;
  int base = 10;
  int paren_depth = 0;
  int produced = 0;
  bool has_eof = false;
  char c, current;

  string_buf = malloc(DEFAULT_STR_LEN);
  if(!string_buf) {
    ret = ZEPTO_TOK_ENOMEM;
    goto out;
  }

  init_list_head(list);
  last = list;

  // read first character
  NEXT;

  while(1) {
    if(CURRENT == ';' && state != ST_STRING) {
      do {
        NEXT;
      } while(CURRENT != '\n');
      NEXT;
    }
    switch(state) {
      case ST_SPACE:
        if(produced > 0 && paren_depth == 0) {
          goto out;
        }
        if(!isspace(CURRENT)) {
          switch(CURRENT) {
            case '(':
              DEBUG("OP_B");
              paren_depth++;
              NEXT;
              NEWTOKEN;
              token->type = TOK_OP_B;
              break;
            case ')':
              DEBUG("CL_B");
              paren_depth--;
              if(paren_depth < 0) {
                ret = ZEPTO_TOK_MISMATCHED_PAREN;
              }
              NEXT;
              NEWTOKEN;
              token->type = TOK_CL_B;
              break;
            case '"':
              NEXT;
              NEWTOKEN;
              token->type = TOK_STRING;
              state = ST_STRING;
              break;
            case '\'':
              NEXT;
              NEWTOKEN;
              token->type = TOK_QUOTE;
              DEBUG("QUOTE");
              break;
            default:
              if(isdigit(CURRENT)) {
                NEWTOKEN;
                token->type = TOK_INTEGER;
                state = ST_NUMBER;
              } else {
                NEWTOKEN;
                token->type = TOK_SYMBOL;
                state = ST_SYMBOL;
              }
              break;
          }
        } else {
          NEXT;
        }
        break;
      case ST_STRING:
        if(CURRENT == '"') {
          PUTSTRING;
          state = ST_SPACE;
        } else if(CURRENT == '\\') {
          state = ST_STR_SLASH;
        } else {
          ADDCHAR(CURRENT);
        }
        NEXT;
        break;
      case ST_STR_SLASH:
        switch(CURRENT) {
          case 'n':
            c = '\n';
            break;
          case 't':
            c = '\t';
            break;
          case 'r':
            c = '\r';
            break;
          case 'b':
            c = '\b';
            break;
          default:
            c = CURRENT;
        }
        state = ST_STRING;
        ADDCHAR(c);
        NEXT;
        break;
      case ST_SYMBOL:
        if(strchr(delim, CURRENT)) {
          PUTSYMBOL;
          state = ST_SPACE;
        } else {
          ADDCHAR(CURRENT);
          NEXT;
        }
        break;
      case ST_NUMBER:
        if(strchr(delim, CURRENT)) {
          PUTNUMBER;
          state = ST_SPACE;
        } else {
          ADDCHAR(CURRENT);
          CHECKNUMBER;
          NEXT;
        }       
        break;
    }
  }

  //TODO: add fault cleanup

  out:

  free(string_buf);
  
  if(ret == ZEPTO_TOK_UNEXPECTED_EOF && state == ST_SPACE &&
     paren_depth == 0) {
    if(produced > 0) {
      DEBUG("Exiting with success and EOF");
      ret = ZEPTO_SUCCESS;
    } else {
      DEBUG("Exiting with EOF");
      ret = EOF;
    }
  } else {
    DEBUG("Exiting with code, ret = %d (%d %d)", ret, state, paren_depth);
  }

  return ret;
}
